import asyncio
import functools
import json
import logging
from typing import Union, Set, Callable, Coroutine, Any, List

from redis import Redis

from .json_encoder import CustomJSONEncoder
from dependencies.cache import redis_be
from env_config import settings
from redis import asyncio as aioredis

__all__ = ['RedisUtil']

Field = Union[int, float, str]
logger = logging.getLogger(__name__)


class RedisUtil:
    """Redis操作工具类"""
    prefix: str = settings.redis_prefix

    redis: Redis = redis_be.redis

    @staticmethod
    async def get_redis(retry_count: int = 0, max_retry_count: int = 3):
        try:
            await RedisUtil.redis.ping()
            return RedisUtil.redis
        except Exception as e:
            if max_retry_count <= retry_count:
                raise e
            redis_url = settings.redis_url
            RedisUtil.redis = aioredis.from_url(redis_url, encoding='utf8', decode_responses=True,
                                                max_connections=1000,
                                                retry_on_timeout=True, health_check_interval=30000)  # 使用你的连接函数
            logger.warning(f"Redis reconnection {retry_count} times")
            return RedisUtil.redis

    @staticmethod
    def get_key(key: str) -> str:
        """key通用前缀"""
        return f'{RedisUtil.prefix}{key}'

    @staticmethod
    async def info(section: Union[str, None] = None) -> dict:
        """Redis服务信息"""
        return await (await RedisUtil.get_redis()).info(section)

    @staticmethod
    async def dbsize() -> dict:
        """当前数据库key数量"""
        return await (await RedisUtil.get_redis()).dbsize()

    @classmethod
    async def set(cls, key: str, value: Field, time: int = None):
        """设置键值对"""
        await (await cls.get_redis()).set(cls.get_key(key), value=value, ex=time)

    @classmethod
    async def get(cls, key: str) -> str:
        """获取key的值"""
        return await (await cls.get_redis()).get(cls.get_key(key))

    @classmethod
    async def exists(cls, *keys: str) -> int:
        """判断多项key是否存在
            返回存在的key数量
        """
        return await (await cls.get_redis()).exists(*(cls.get_key(key) for key in keys))

    @classmethod
    async def sset(cls, key: str, *values: Field) -> int:
        """将数据放入set缓存
            返回添加的数量
        """
        return await (await cls.get_redis()).sadd(cls.get_key(key), *values)

    @classmethod
    async def sget(cls, key: str) -> Set:
        """根据key获取Set中的所有值"""
        return await (await cls.get_redis()).smembers(cls.get_key(key))

    @classmethod
    async def sdel(cls, key: str, *values: Field):
        """根据key获取Set中的所有值"""
        await (await cls.get_redis()).srem(cls.get_key(key), *values)

    @classmethod
    async def hmset(cls, key: str, mapping: dict, time: int = None) -> int:
        """设置key, 通过字典的方式设置多个field, value对
            返回添加的数量
        """
        cnt = await (await cls.get_redis()).hset(cls.get_key(key), mapping=mapping)
        if time and time > 0:
            await cls.expire(key, time)
        return cnt

    @classmethod
    async def hset(cls, key: str, field: str, value: Field, time: int = None) -> int:
        """向hash表中放入数据,如果不存在将创建
            返回添加的数量
        """
        return await cls.hmset(key, mapping={field: value}, time=time)

    @classmethod
    async def hget(cls, key: str, field: str) -> str:
        """获取key中field域的值"""
        return await (await cls.get_redis()).hget(cls.get_key(key), field)

    @classmethod
    async def hexists(cls, key: str, field: str) -> bool:
        """判断key中有没有field域名"""
        return await (await cls.get_redis()).hexists(cls.get_key(key), field)

    @classmethod
    async def hdel(cls, key: str, *fields: str) -> int:
        """删除hash表中的值
            返回删除的数量
        """
        return await (await cls.get_redis()).hdel(cls.get_key(key), *fields)

    @classmethod
    async def ttl(cls, key: str) -> int:
        """根据key获取过期时间"""
        try:
            rd = await cls.get_redis()
            rkey = cls.get_key(key)
            return await rd.ttl(rkey)
        except Exception as e:
            raise e


    @classmethod
    async def expire(cls, key: str, time: int):
        """指定缓存失效时间"""
        await (await cls.get_redis()).expire(cls.get_key(key), time)

    @classmethod
    async def delete(cls, *keys: str):
        """删除一个或多个键"""
        await (await cls.get_redis()).delete(*(cls.get_key(key) for key in keys))

    @classmethod
    async def zget(cls, key: str, field: str) -> Any:
        return await (await cls.get_redis()).zscore(cls.get_key(key), field)

    @classmethod
    async def zget_keys(cls, key: str, value: Field) -> Any:
        return await (await cls.get_redis()).zrangebyscore(cls.get_key(key), value, value)

    @classmethod
    async def zadd(cls, key: str, field: str, value: Field):
        await (await cls.get_redis()).zadd(cls.get_key(key), {field: value})

    @classmethod
    async def zdel(cls, key: str, field: str):
        await (await cls.get_redis()).zrem(cls.get_key(key), field)

    @classmethod
    async def delete_by_prefix(cls, prefix: str) -> None:
        """根据prefix删除相关的缓存"""
        keys_to_delete = await cls.scan_keys(prefix)  # 你可以用scan获取相关的缓存键

        if keys_to_delete:
            await cls.delete(*keys_to_delete)  # 删除所有找到的缓存键

    @classmethod
    async def scan_keys(cls, pattern: str) -> list:
        """扫描与给定前缀匹配的所有keys"""
        # Redis的SCAN命令可以用来查找匹配的键
        redis = await cls.get_redis()
        cursor, keys = await redis.scan(cursor=0, match=pattern)
        return keys

    @classmethod
    def cache(cls, main_key: str, expire: int = 7200):
        """缓存装饰器"""

        def decorator(func):
            @functools.wraps(func)
            async def wrapper(*args, **kwargs):
                key = cls.generate_cache_key(func, args[1:], kwargs)
                cached_result = await cls.hget(main_key, key)
                # await RedisUtil.delete(main_key)
                if cached_result:
                    return json.loads(cached_result)
                result = await func(*args, **kwargs)
                await cls.hset(main_key, key, json.dumps(result, cls=CustomJSONEncoder), expire)
                return result

            return wrapper

        return decorator

    @classmethod
    def generate_cache_key(cls, func: Callable, args, kwargs) -> str:
        """生成缓存键名"""
        try:
            key = f"{func.__name__}:{json.dumps(args, cls=CustomJSONEncoder)}:{json.dumps(kwargs, cls=CustomJSONEncoder)}"
        except TypeError as e:
            raise ValueError(f"Error generating cache key: {e}")
        return cls.get_key(key)

    @classmethod
    def invalidate_cache(cls, main_key: str):
        """缓存失效装饰器"""

        def decorator(func: Callable[..., Coroutine[Any, Any, Any]]):
            @functools.wraps(func)
            async def wrapper(*args, **kwargs):
                result = await func(*args, **kwargs)
                await cls.delete(main_key)
                return result

            return wrapper

        return decorator
